package cn.thread;

import java.util.Stack;

/**
 * 产品存储区 所有消费者和生产者共享
 *
 * @author dunlingzi
 */
public class ProductStack {

    /**
     * 标志存储区是否打开--
     */
    private boolean stackOpen = false;

    /**
     * 存储区能容纳的最大产品数
     */
    private int capacity = 10;

    /**
     * 当前的产品数
     */
    private int current = 0;

    /**
     * 存放产品的容器
     */
    private Product[] productArray = new Product[capacity];

    /**
     * 存储区关闭后使用外部存储区（备用区）
     */
    private Stack<Product> externalStack = new Stack<>();

    /**
     * 构造器
     */
    public ProductStack() {

    }

    public ProductStack(int capacity) {
        this.productArray = new Product[capacity];
    }

    /**
     * 存放产品
     * 对象锁 相当于方法中加synchronized(this) {方法体}
     * 所有集成Object的类都有一个锁标记（lock flag） ,当多个线程对同一个对象进行访问时
     * 如果遇到同步代码块，会先检查锁标记是否打开，如果打开，对象就被放到锁池中等待
     * 等其它同步代码块释放了锁标记后才继续执行；如果没有打开，则为对象添加一个锁标记，
     * 然后再执行
     */
    public synchronized void push(Product pt) {
        //当存储区开启的状态，存储区的产品数量达到最大值时，需要将生产者生产线程进行等待阻塞
        while (stackOpen && current == productArray.length) {
            try {
                //线程释放锁标记，被放入等待池，当同一对象中的其它同步代码块调用
                //notify/notifyAll时，线程被放到锁池中，等待其它同步代码块释放锁标记后执行
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //存储区未打开，外部存储区存储
        if (!stackOpen) {
            //外部存储区存储
            externalStack.push(pt);
            System.out.println("由于存储区已关闭，第" + pt.getId() + "号导出到外部存储区");
            return;
        }
        productArray[current] = pt;
        String threadName = Thread.currentThread().getName();
        if ("externalStackImportThread".equals(threadName)) {
            System.out.println("第" + pt.getId() + "号已从外部存储区导入");
        } else {
            System.out.println(threadName + "生产的：第" + pt.getId() + "号已入库");
        }
        ++current;
        /**
         * 释放本对象等待池中的所有线程，进行锁池，等push释放锁标记后，共同竞争以进入running状态
         * 此时，存储区至少存在一个产品，通知在pop中等待的线程，等push结束，可以相互竞争继续执行
         */
        this.notifyAll();
    }

    /**
     * 取出产品
     */
    public synchronized Product pop() {
        //存储区开启，且当前存储区中没有产品的情况下，所有取产品的线程阻塞在等待池中
        while (stackOpen && current == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (!stackOpen) {
            return null;
        }
        --current;
        //此时，存储区至少还有一个产品可存储，所以通知在push中等待的线程，pop一结束，就可以相互竞争，继续执行
        this.notifyAll();
        Product pt = productArray[current];
        System.out.println("--------------" + Thread.currentThread().getName() + "消费的：第" + pt.getId() + "号已出库");
        return pt;
    }

    /**
     * 打开存储区
     */
    public void openStack() {
        System.out.println("---------------存储区已打开！现有产品数：" + getCurrent() + "----------");
        stackOpen = true;
        //导入外部存储区的产品
        if (!externalStack.isEmpty()) {
            improtExternalStack();
        }
    }

    /**
     * 外部存储区不为空，从外部存储区导入产品到存储区
     */
    private void improtExternalStack() {
        //导入外部存储区产品进入存储区
        Thread thread = new Thread(() -> {
            while (!externalStack.isEmpty()) {
                Product pt = externalStack.pop();
                push(pt);
            }
        });
        thread.setName("externalStackImportThread");
        thread.start();
    }

    public synchronized void closeStack() {
        stackOpen = false;
        this.notifyAll();
        System.out.println("==========存储区已关闭！现有产品数：" + getCurrent() + "=============");
    }

    public boolean isStackOpen() {
        return stackOpen;
    }

    public int getMaxProduct() {
        return capacity;
    }

    public int getCurrent() {
        return current;
    }

    public int getExternalStackProduct() {
        return externalStack.size();
    }
}